package cn.iocoder.yudao.module.system.job;

import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.module.gov.dal.dataobject.resourcecatalog.ResourceCatalogDO;
import cn.iocoder.yudao.module.gov.dal.dataobject.resourcedirectory.ResourceDirectoryDO;
import cn.iocoder.yudao.module.system.convert.GovResourceConverter;
import cn.iocoder.yudao.module.system.dal.dataobject.nodeconfig.NodeConfigDO;
import cn.iocoder.yudao.module.system.service.external.ExternalApiService;
import cn.iocoder.yudao.module.gov.service.resourcecatalog.ResourceCatalogService;
import cn.iocoder.yudao.module.gov.service.resourcedirectory.ResourceDirectoryService;
import cn.iocoder.yudao.module.system.service.nodeconfig.NodeConfigService;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 政务资源目录同步定时任务
 * 同步逻辑：1. 删除旧数据 → 2. 同步目录树并获取ID列表 → 3. 循环每个目录树ID同步目录数据 → 4. 批量插入 → 5. 提取所有dept_id另作处理
 */
@Component
public class GovResourceSyncTask implements JobHandler {
    private static final Logger log = LoggerFactory.getLogger(GovResourceSyncTask.class);

    // 目录接口单次最大获取条数
    private static final int CATALOG_MAX_PAGE_SIZE = 100;
    // 批量插入批次大小
    private static final int BATCH_INSERT_SIZE = 500;
    // 接口调用重试次数
    private static final int API_RETRY_COUNT = 2;

    @Autowired
    private ExternalApiService externalApiService;

    @Autowired
    private ResourceCatalogService resourceCatalogService;

    @Autowired
    private ResourceDirectoryService resourceDirectoryService;

    @Autowired
    private GovResourceConverter resourceConverter;
    @Resource
    private NodeConfigService nodeConfigService;

    /**
     * 定时执行同步任务，每天凌晨2点执行一次
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String execute(String param) throws Exception {
        Instant startTime = Instant.now();
        log.info("==================== 开始政务资源目录全量同步任务 ====================");

        // 存储所有目录数据，统一批量插入
        List<ResourceDirectoryDO> allDirectoryList = new ArrayList<>();
        // 存储所有提取的dept_id（使用Set自动去重）
        Set<Long> allDeptIds = new HashSet<>();

        try {
            // 1. 全量删除旧数据
            cleanupOldData();

            // 2. 同步目录树数据并获取ID列表
            List<Long> catalogIdList = syncMenuList();
            int catalogCount = catalogIdList.size();
            log.info("获取到有效目录树ID共 {} 个，准备循环同步目录数据", catalogCount);

            if (catalogCount == 0) {
                log.warn("未获取到目录树ID，终止目录数据同步");
                return "同步完成：目录树0条，资源目录0条，耗时" +
                        Duration.between(startTime, Instant.now()).toMillis() + "ms";
            }

            // 3. 循环每个目录树ID同步目录数据，同时提取dept_id
            for (Long catalogId : catalogIdList) {
                log.info("开始同步目录树ID=[{}]的目录数据（剩余 {} 个ID待同步）",
                        catalogId, catalogIdList.size() - catalogIdList.indexOf(catalogId) - 1);

                // 同步当前目录树ID对应的目录数据，并获取该批次的dept_id
                Map<String, Object> result = syncCatalogByCatalogId(catalogId);
                List<ResourceDirectoryDO> directoryList = (List<ResourceDirectoryDO>) result.get("directoryList");
                Set<Long> deptIds = (Set<Long>) result.get("deptIds");

                if (!directoryList.isEmpty()) {
                    allDirectoryList.addAll(directoryList);
                    allDeptIds.addAll(deptIds); // 合并到全局dept_id集合
                    log.info("目录树ID=[{}]同步完成，新增 {} 条数据，累计同步 {} 条，累计获取dept_id {} 个",
                            catalogId, directoryList.size(), allDirectoryList.size(), allDeptIds.size());
                } else {
                    log.warn("目录树ID=[{}]未查询到有效目录数据", catalogId);
                }
            }

            // 4. 批量插入所有目录数据
            int directoryCount = batchInsertDirectories(allDirectoryList);

            // 5. 处理提取到的所有dept_id（核心：这里是你的后续处理逻辑）
            handleAllDeptIds(allDeptIds);

            // 6. 同步完成统计
            long costTime = Duration.between(startTime, Instant.now()).toMillis();
            log.info("==================== 政务资源目录全量同步任务完成 ====================");
            log.info("同步统计：目录树{}条，资源目录{}条，提取dept_id共{}个，总耗时{}ms",
                    catalogCount, directoryCount, allDeptIds.size(), costTime);

            return String.format("同步成功：目录树%d条，资源目录%d条，提取dept_id共%d个，耗时%dms",
                    catalogCount, directoryCount, allDeptIds.size(), costTime);
        } catch (Exception e) {
            log.error("==================== 政务资源目录全量同步任务失败 ====================", e);
            throw new RuntimeException("同步任务异常终止，已触发事务回滚", e);
        }
    }

    /**
     * 全量删除数据库中已有的旧数据
     */
    private void cleanupOldData() {
        log.info("开始删除数据库旧数据...");

        // 先删目录数据（子表）
        int deletedDirectoryCount = resourceDirectoryService.deleteAll();
        log.info("目录数据删除完成，共删除 {} 条记录", deletedDirectoryCount);

        // 再删目录树数据（主表）
        int deletedCatalogCount = resourceCatalogService.deleteAll();
        log.info("目录树数据删除完成，共删除 {} 条记录", deletedCatalogCount);
    }

    /**
     * 同步目录树数据并返回ID列表
     */
    private List<Long> syncMenuList() {
        log.info("开始同步目录树数据（接口地址：{}）", "https://59.208.147.193:31950/com/cmcc/dataexchange/manage/portal/share/getMenuList");

        try {
            // 调用接口获取数据（带重试机制）
            String menuListResult = callApiWithRetry("https://59.208.147.193:31950/com/cmcc/dataexchange/manage/portal/share/getMenuList", "GET", null);
            log.debug("目录树接口返回原始数据：{}", menuListResult);

            // 解析返回结果
            JSONObject resultJson = JSONObject.parseObject(menuListResult);
            validateApiResponse(resultJson, "目录树");

            JSONArray menuList = resultJson.getJSONArray("data");
            if (menuList == null || menuList.isEmpty()) {
                log.warn("目录树接口返回空数据");
                return new ArrayList<>();
            }

            // 转换为实体类列表
            List<ResourceCatalogDO> catalogList = resourceConverter.convertCatalogList(menuList);
            if (catalogList.isEmpty()) {
                log.warn("目录树数据转换后为空");
                return new ArrayList<>();
            }

            // 分批插入目录树数据
            batchInsertCatalogs(catalogList);
            log.info("目录树数据插入完成，共插入 {} 条", catalogList.size());

            // 提取目录树ID列表（过滤空值）
            return catalogList.stream()
                    .map(ResourceCatalogDO::getId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("同步目录树数据失败", e);
            throw new RuntimeException("同步目录树数据失败", e);
        }
    }

    /**
     * 根据目录树ID同步对应的目录数据（分页查询），并提取该批次的dept_id
     * @return 包含目录数据列表和dept_id集合的Map
     */
    private Map<String, Object> syncCatalogByCatalogId(Long catalogId) {
        List<ResourceDirectoryDO> directoryList = new ArrayList<>();
        Set<Long> deptIds = new HashSet<>(); // 存储当前目录树ID下的所有dept_id
        int currentPage = 1;
        int totalPage = 1;

        try {
            while (currentPage <= totalPage) {
                log.debug("同步目录树ID=[{}]的目录数据：第{}页/共{}页",
                        catalogId, currentPage, totalPage);

                // 构建请求参数
                Map<String, Object> params = new HashMap<>(5);
                params.put("menuId", catalogId);
                params.put("currentPage", currentPage);
                params.put("pageSize", CATALOG_MAX_PAGE_SIZE);
                params.put("sortType", 0);

                // 调用接口获取数据
                String catalogResult = callApiWithRetry("https://59.208.147.193:31950/com/cmcc/dataexchange/manage/portal/share/getCatalogList", "POST", params);
                log.debug("目录树ID=[{}]第{}页接口返回数据：{}", catalogId, currentPage, catalogResult);

                // 解析返回结果
                JSONObject resultJson = JSONObject.parseObject(catalogResult);
                validateApiResponse(resultJson, "目录数据（ID=" + catalogId + "，第" + currentPage + "页）");

                JSONObject data = resultJson.getJSONObject("data");
                if (data == null) {
                    log.warn("目录树ID=[{}]第{}页返回data为空，终止该ID查询", catalogId, currentPage);
                    break;
                }

                // 更新分页信息
                totalPage = data.getInteger("totalPage") == null ? 0 : data.getInteger("totalPage");
                JSONArray catalogDataList = data.getJSONArray("data");

                // 转换并收集数据，同时提取dept_id
                if (catalogDataList != null && !catalogDataList.isEmpty()) {
                    List<ResourceDirectoryDO> pageData = resourceConverter.convertDirectoryList(catalogDataList);
                    // 补充目录树ID到目录数据中，并提取dept_id
                    pageData.forEach(directory -> {
                        directory.setMenuId(catalogId);
                        // 提取dept_id并添加到集合（自动去重）
                        if (directory.getDeptId() != null) {
                            deptIds.add(directory.getDeptId());
                        }
                    });
                    directoryList.addAll(pageData);
                }

                currentPage++;
            }

            log.info("目录树ID=[{}]同步完成，共获取 {} 条目录数据，提取dept_id {} 个",
                    catalogId, directoryList.size(), deptIds.size());

            // 将目录数据和dept_id集合返回
            Map<String, Object> result = new HashMap<>();
            result.put("directoryList", directoryList);
            result.put("deptIds", deptIds);
            return result;
        } catch (Exception e) {
            log.error("同步目录树ID=[{}]的目录数据失败（当前页码：{}）", catalogId, currentPage, e);
            throw new RuntimeException("同步目录树ID=" + catalogId + "的目录数据失败", e);
        }
    }

    /**
     * 分批插入目录树数据
     */
    private void batchInsertCatalogs(List<ResourceCatalogDO> catalogList) {
        for (int i = 0; i < catalogList.size(); i += BATCH_INSERT_SIZE) {
            int end = Math.min(i + BATCH_INSERT_SIZE, catalogList.size());
            List<ResourceCatalogDO> batchList = catalogList.subList(i, end);
            resourceCatalogService.saveBatch(batchList);
            log.debug("目录树分批插入：第{}批，数量：{}", i / BATCH_INSERT_SIZE + 1, batchList.size());
        }
    }

    /**
     * 分批插入目录数据
     */
    private int batchInsertDirectories(List<ResourceDirectoryDO> directoryList) {
        if (directoryList.isEmpty()) {
            log.warn("无目录数据可插入");
            return 0;
        }

        for (int i = 0; i < directoryList.size(); i += BATCH_INSERT_SIZE) {
            int end = Math.min(i + BATCH_INSERT_SIZE, directoryList.size());
            List<ResourceDirectoryDO> batchList = directoryList.subList(i, end);
            resourceDirectoryService.saveBatch(batchList);
            log.debug("目录数据分批插入：第{}批，数量：{}", i / BATCH_INSERT_SIZE + 1, batchList.size());
        }

        return directoryList.size();
    }

    /**
     * 带重试机制的API调用
     */
    private String callApiWithRetry(String url, String method, Map<String, Object> params) throws Exception {
        int retry = 0;
        while (retry < API_RETRY_COUNT) {
            try {
                if ("GET".equalsIgnoreCase(method)) {
                    return externalApiService.get(url);
                } else if ("POST".equalsIgnoreCase(method)) {
                    return externalApiService.post(url, params);
                } else {
                    throw new RuntimeException("不支持的请求方式：" + method);
                }
            } catch (Exception e) {
                retry++;
                if (retry >= API_RETRY_COUNT) {
                    throw new RuntimeException("API调用失败，已重试" + API_RETRY_COUNT + "次", e);
                }
                log.warn("API调用失败，将进行第{}次重试", retry, e);
                Thread.sleep(1000 * retry); // 指数退避重试
            }
        }
        throw new RuntimeException("API调用重试次数耗尽");
    }

    /**
     * 验证API响应是否正常
     */
    private void validateApiResponse(JSONObject resultJson, String apiDesc) {
        if (resultJson == null) {
            throw new RuntimeException(apiDesc + "接口返回空结果");
        }

        Integer code = resultJson.getInteger("code");
        Boolean success = resultJson.getBoolean("success");
        String msg = resultJson.getString("msg");

        if (code == null || code != 200 || (success != null && !success)) {
            throw new RuntimeException(apiDesc + "接口返回异常：code=" + code
                    + ", success=" + success + ", msg=" + msg);
        }
    }

    /**
     * 处理提取到的所有dept_id（核心：在这里实现你的后续业务逻辑）
     */
    private void handleAllDeptIds(Set<Long> deptIds) {
        if (deptIds.isEmpty()) {
            log.warn("未提取到任何dept_id，无需处理");
            return;
        }

        log.info("开始处理提取到的dept_id，共 {} 个（已去重）", deptIds.size());

        // 1. 打印所有dept_id（调试用）
        log.debug("提取到的dept_id列表：{}", deptIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(",")));

        // 2. 查询审核节点（源单位审核，code=RIGHT）
        NodeConfigDO nodeConfigDO = nodeConfigService.getNodeByCode("RIGHT");
        if (nodeConfigDO == null) {
            log.error("未查询到code=RIGHT的审核节点配置，终止dept_id补充逻辑");
            return;
        }
        log.debug("查询到code=RIGHT的审核节点：{}", JSONObject.toJSONString(nodeConfigDO));

        // 3. 解析auditor字段（新的JSON格式：{"部门ID":{"dept":"部门","telephone":"座机","mobile":"手机"},...}）
        String auditorJson = nodeConfigDO.getAuditor();
        final Map<String, Map<String, String>> auditorMap = new HashMap<>(); // 存储：部门ID -> 审批人信息Map
        if (StringUtils.isNotBlank(auditorJson)) {
            try {
                // 解析JSON为临时Map，避免直接给final的auditorMap赋值
                Map<String, Map<String, String>> parsedMap = JSONObject.parseObject(
                        auditorJson, new TypeReference<Map<String, Map<String, String>>>() {});
                auditorMap.putAll(parsedMap); // 用putAll合并解析结果，不改变auditorMap引用
                log.debug("解析现有auditor成功，共 {} 个部门配置", auditorMap.size());
            } catch (Exception e) {
                log.error("解析auditor JSON失败，auditor内容：{}", auditorJson, e);
                return;
            }
        } else {
            log.debug("现有auditor为空，直接添加所有dept_id");
        }

        // 4. 筛选待添加的部门ID：已提取的deptIds - 现有auditor中的部门ID（去重并排除已存在）
        Set<String> existingDeptIds = auditorMap.keySet(); // 现有部门ID（String类型，与JSON解析一致）
        Set<Long> toAddDeptIds = deptIds.stream()
                .filter(deptId -> !existingDeptIds.contains(String.valueOf(deptId))) // 排除已存在部门
                .collect(Collectors.toSet()); // 待添加的部门ID集合
        if (toAddDeptIds.isEmpty()) {
            log.info("所有提取的dept_id已存在于auditor中，无需新增");
            return;
        }
        log.info("待添加到auditor的部门ID数量：{}，列表：{}",
                toAddDeptIds.size(), toAddDeptIds.stream().map(String::valueOf).collect(Collectors.joining(",")));

        // 5. 补充待添加的部门到auditorMap（使用新的JSON结构）
        toAddDeptIds.forEach(deptId -> {
            Map<String, String> auditorInfo = new HashMap<>();
            auditorInfo.put("auditor", ""); // 用户昵称留空
            auditorInfo.put("telephone", ""); // 座机号留空
            auditorInfo.put("mobile", ""); // 手机号留空
            auditorMap.put(String.valueOf(deptId), auditorInfo);
        });
        log.debug("补充后的auditorMap：{}", JSONObject.toJSONString(auditorMap));

        // 6. 重新组装auditor为JSON字符串，并更新到节点配置中
        String updatedAuditorJson = JSONObject.toJSONString(auditorMap);
        nodeConfigDO.setAuditor(updatedAuditorJson); // 更新节点配置的auditor字段
        boolean updateResult = nodeConfigService.updateById(nodeConfigDO); // 调用服务更新数据库

        // 7. 打印更新结果，便于排查问题
        if (updateResult) {
            log.info("auditor更新成功！更新后共 {} 个部门配置，JSON内容：{}",
                    auditorMap.size(), updatedAuditorJson);
        } else {
            log.error("auditor更新失败，节点ID：{}，更新内容：{}",
                    nodeConfigDO.getId(), updatedAuditorJson);
        }

        log.info("dept_id处理完成");
    }
}
